package cn.sylinx.horm.example.benchmark;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import cn.sylinx.horm.core.SqlClient;
import cn.sylinx.horm.dialect.fs.FS;
import cn.sylinx.horm.example.benchmark.horm.SqlConsts;
import cn.sylinx.horm.example.benchmark.horm.UserHORM;
import cn.sylinx.horm.example.benchmark.mybatis.UserMapper;
import cn.sylinx.horm.example.benchmark.mybatis.UserMybatis;
import cn.sylinx.horm.example.benchmark.mybatis.UserService;
import cn.sylinx.horm.resource.ClasspathSqlResource;
import cn.sylinx.horm.util.ParamMapBuilder;

@Profile("benchmark")
@Component
public class Benchmark implements CommandLineRunner {

    private static final int QUERY_COUNT = 1000;
    private static final int TOTAL_COUNT = 20000;
    private static final int SIZE = 1000;
    private static final AtomicLong ID = new AtomicLong(1);

    private SqlSessionFactory sqlSessionFactory;
    private UserService userService;
    private SqlClient sqlClient;

    public Benchmark(SqlSessionFactory sqlSessionFactory, UserService userService, SqlClient sqlClient) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.userService = userService;
        this.sqlClient = sqlClient;
    }

    @Override
    public void run(String... args) throws Exception {
        initData();

        warmUp(); // 预热

        benchmark(); // 测试
    }

    void benchmark() {

        long timeMillis;
        System.out.println();
        System.out.println();
        System.out.println("---------------------selectOne:");

        for (int i = 0; i < 10; i++) {
            System.out.println("---------------");

            timeMillis = System.currentTimeMillis();
            selectOneHorm();
            System.out.println(">>>>>>>selectOneHorm:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            selectOneHormAviatorSql();
            System.out.println(">>>>>>>selectOneHormAviatorSql:" + (System.currentTimeMillis() - timeMillis));

            timeMillis = System.currentTimeMillis();
            selectOneHormSystemSql();
            System.out.println(">>>>>>>selectOneHormSystemSql:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            selectOneMybatis();
            System.out.println(">>>>>>>selectOneMybatis:" + (System.currentTimeMillis() - timeMillis));
        }

        System.out.println();
        System.out.println();
        System.out.println("---------------------selectList100:");

        for (int i = 0; i < 10; i++) {
            System.out.println("---------------");

            timeMillis = System.currentTimeMillis();
            select100Horm();
            System.out.println(">>>>>>>select100Horm:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            select100HormAviatorSql();
            System.out.println(">>>>>>>select100HormAviatorSql:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            select100HormSystemSql();
            System.out.println(">>>>>>>select100HormSystemSql:" + (System.currentTimeMillis() - timeMillis));

            timeMillis = System.currentTimeMillis();
            select100Mybatis();
            System.out.println(">>>>>>>select100Mybatis:" + (System.currentTimeMillis() - timeMillis));
        }

        System.out.println();
        System.out.println();
        System.out.println("---------------------selectList1000:");

        for (int i = 0; i < 10; i++) {
            System.out.println("---------------");

            timeMillis = System.currentTimeMillis();
            select1000Horm();
            System.out.println(">>>>>>>select1000Horm:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            select1000HormAviatorSql();
            System.out.println(">>>>>>>select1000HormAviatorSql:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            select1000HormSystemSql();
            System.out.println(">>>>>>>select1000HormSystemSql:" + (System.currentTimeMillis() - timeMillis));

            timeMillis = System.currentTimeMillis();
            select1000Mybatis();
            System.out.println(">>>>>>>select1000Mybatis:" + (System.currentTimeMillis() - timeMillis));
        }
        
        System.out.println();
        System.out.println();
        System.out.println("---------------------selectList10000:");

        for (int i = 0; i < 10; i++) {
            System.out.println("---------------");

            timeMillis = System.currentTimeMillis();
            select10000Horm();
            System.out.println(">>>>>>>select10000Horm:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            select10000HormAviatorSql();
            System.out.println(">>>>>>>select10000HormAviatorSql:" + (System.currentTimeMillis() - timeMillis));
            
            timeMillis = System.currentTimeMillis();
            select10000HormSystemSql();
            System.out.println(">>>>>>>select10000HormSystemSql:" + (System.currentTimeMillis() - timeMillis));

            timeMillis = System.currentTimeMillis();
            select10000Mybatis();
            System.out.println(">>>>>>>select10000Mybatis:" + (System.currentTimeMillis() - timeMillis));
        }

        System.out.println();
        System.out.println();
        System.out.println("---------------------update:");

        for (int i = 0; i < 10; i++) {
            System.out.println("---------------");

            timeMillis = System.currentTimeMillis();
            updateHormFS();
            System.out.println(">>>>>>>updateHormFS:" + (System.currentTimeMillis() - timeMillis));

            timeMillis = System.currentTimeMillis();
            updateHormEntity();
            System.out.println(">>>>>>>updateHormEntity:" + (System.currentTimeMillis() - timeMillis));

            timeMillis = System.currentTimeMillis();
            updateMybatis();
            System.out.println(">>>>>>>updateMybatis:" + (System.currentTimeMillis() - timeMillis));
        }
    }

    void warmUp() {
        selectOneMybatis();
        select100Mybatis();
        
        selectOneHorm();
        select100Horm();
    }

    void selectOneMybatis() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            userService.select(ThreadLocalRandom.current().nextLong(10000), 1);
        }
    }

    void selectOneHorm() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.query(FS.of(UserHORM.class).where(UserHORM::getId).gt(ThreadLocalRandom.current().nextLong(10000))
                    .limit(1));
        }
    }

    void selectOneHormSystemSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 1).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applySystemResource(SqlConsts.BENCHMARK_SYSTEM), params,
                    UserHORM.class);
        }
    }

    void selectOneHormAviatorSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 1).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applyAviatorResource(SqlConsts.BENCHMARK_AVIATOR), params,
                    UserHORM.class);
        }
    }

    void select100Mybatis() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            userService.select(ThreadLocalRandom.current().nextLong(10000), 100);
        }
    }

    void select1000Mybatis() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            userService.select(ThreadLocalRandom.current().nextLong(10000), 1000);
        }
    }
    
    void select10000Mybatis() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            userService.select(ThreadLocalRandom.current().nextLong(10000), 10000);
        }
    }

    void select100Horm() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.query(FS.of(UserHORM.class).where(UserHORM::getId).gt(ThreadLocalRandom.current().nextLong(10000))
                    .limit(100));
        }
    }
    
    void select100HormSystemSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 100).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applySystemResource(SqlConsts.BENCHMARK_SYSTEM), params,
                    UserHORM.class);
        }
    }
    
    void select100HormAviatorSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 100).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applyAviatorResource(SqlConsts.BENCHMARK_AVIATOR), params,
                    UserHORM.class);
        }
    }

    void select1000Horm() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.query(FS.of(UserHORM.class).where(UserHORM::getId).gt(ThreadLocalRandom.current().nextLong(10000))
                    .limit(1000));
        }
    }
    
    void select10000Horm() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.query(FS.of(UserHORM.class).where(UserHORM::getId).gt(ThreadLocalRandom.current().nextLong(10000))
                    .limit(10000));
        }
    }
    
    void select1000HormSystemSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 1000).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applySystemResource(SqlConsts.BENCHMARK_SYSTEM), params,
                    UserHORM.class);
        }
    }
    
    void select10000HormSystemSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 10000).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applySystemResource(SqlConsts.BENCHMARK_SYSTEM), params,
                    UserHORM.class);
        }
    }
    
    void select1000HormAviatorSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 1000).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applyAviatorResource(SqlConsts.BENCHMARK_AVIATOR), params,
                    UserHORM.class);
        }
    }
    
    void select10000HormAviatorSql() {
        Map<String, Object> params = new ParamMapBuilder().put("id", ThreadLocalRandom.current().nextLong(10000))
                .put("count", 10000).build();
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.queryList(ClasspathSqlResource.applyAviatorResource(SqlConsts.BENCHMARK_AVIATOR), params,
                    UserHORM.class);
        }
    }

    void updateHormFS() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            sqlClient.update(FS.of(UserHORM.class).set(UserHORM::getUserName, "user_name_u_" + i)
                    .set(UserHORM::getUpass, "upass_u_" + i).where(UserHORM::getId)
                    .eq(ThreadLocalRandom.current().nextLong(10000)));
        }
    }

    void updateHormEntity() {
        for (int i = 0; i < QUERY_COUNT; ++i) {

            UserHORM u = new UserHORM();
            u.setId(ThreadLocalRandom.current().nextLong(10000));
            u.setUserName("user_name_u_" + i);
            u.setUpass("upass_u_" + i);
            sqlClient.update(u);
        }
    }

    void updateMybatis() {
        for (int i = 0; i < QUERY_COUNT; ++i) {
            userService.update(ThreadLocalRandom.current().nextLong(10000), "user_name_u_" + i, "upass_u_" + i);
        }
    }

    void initData() {
        initDataMybatis();
        initDataHorm();
    }

    void initDataHorm() {
        sqlClient.delete(FS.of(UserHORM.class));
        int size = SIZE;
        int count = TOTAL_COUNT / size;
        for (int i = 0; i < count; ++i) {
            addBatchHorm(i, size);
        }
    }

    void initDataMybatis() {
        userService.deleteAllData();
        int size = SIZE;
        int count = TOTAL_COUNT / size;
        for (int i = 0; i < count; ++i) {
            addBatchMybatis(i, size);
        }
    }

    void addBatchHorm(int count, int size) {

        List<UserHORM> userList = new ArrayList<>();
        for (int i = 0; i < size; ++i) {
            String postfix = count + "_" + i;
            userList.add(
                    UserHORM.of(ID.getAndIncrement(), "username" + postfix, i / 2 == 0 ? "男" : "女", "upass" + postfix));
        }
        sqlClient.insertBatch(userList);
    }

    void addBatchMybatis(int count, int size) {
        List<UserMybatis> userList = new ArrayList<>();
        for (int i = 0; i < size; ++i) {
            String postfix = count + "_" + i;
            userList.add(UserMybatis.of(ID.getAndIncrement(), "username" + postfix, i / 2 == 0 ? "男" : "女",
                    "upass" + postfix));
        }
        insertBatch(userList);
    }

    void insertBatch(List<UserMybatis> userList) {
        SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        try {
            for (UserMybatis user : userList) {
                mapper.save(user);
            }
            sqlSession.commit();
        } catch (Exception e) {
            System.out.println("批量导入数据异常，事务回滚");
            sqlSession.rollback();
        } finally {
            sqlSession.close();
        }
    }
}
